<?php

/**
 * MyHammer IndexedMemcache
 *
 * LICENSE
 *
 * This source file is subject to the GNU General Public License Version 3
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.myhammer.de/opensource/license/gpl.txt
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to opensource@myhammer.de so we can send you a copy immediately.
 *
 * @category   MyHammer
 * @package    IndexedMemcache
 * @subpackage Memcache
 * @license    http://www.myhammer.de/opensource/license/gpl.txt GNU General Public License Version 3
 * @version    1.0
 * @author     Steven Berg <berg@myhammer.de>
 */

class cMemCacheSearch {
    const WHERE_LIKE = 'LIKE';
    const WHERE_EQUALS = '=';
    const WHERE_GREATER = '>';
    const WHERE_LOWER = '<';
    const WHERE_GREATER_EQUALS = '>=';
    const WHERE_LOWER_EQUALS = '<=';
    const WHERE_NOT_EQUALS = '!=';
    const WHERE_IS_NULL = 'null';
    const WHERE_IS_NOT_NULL = '!null';

    const _COLUMN = 0;
    const _VALUE = 1;
    const _TYPE = 2;
    const _AND_OR = 3;
    const _DATA = 4;

    const _NOTHING = 0;
    const _AND = 1;
    const _OR = 2;

    private $aWhere = array();
    private $oMemModel = null;

    /**
     * add Where Statement
     *
     * @param string $sColumn
     * @param mixed $mValue
     * @param constants $cWhereType
     */
    public function addWhere($sColumn, $mValue, $cWhereType) {
        $this->aWhere[] = $this->createWhereArray($sColumn, $mValue, $cWhereType, self::_NOTHING);
    }

    /**
     * add And Where Statement
     *
     * @param string $sColumn
     * @param mixed $mValue
     * @param constants $cWhereType
     */
    public function addWhereAnd($sColumn, $mValue, $cWhereType) {
        $this->aWhere[] = $this->createWhereArray($sColumn, $mValue, $cWhereType, self::_AND);
    }

    /**
     * add Or Where Statement
     *
     * @param string $sColumn
     * @param mixed $mValue
     * @param constants $cWhereType
     */
    public function addWhereOr($sColumn, $mValue, $cWhereType) {
        $this->aWhere[] = $this->createWhereArray($sColumn, $mValue, $cWhereType, self::_OR);
    }

    /**
     * select
     *
     * @param mixed $mValue
     * @return array
     */
    public function select($mValue) {
        cProfiler::startPoint('search');
        $this->oMemModel = $mValue;
        $aLinkIds = $this->search();
        cProfiler::endPoint('search');
        return $this->combinateAndOr($aLinkIds);
    }

    /**
     * add And/Or Statement Result
     *
     * @param array $aLinkIds
     * @return array
     */
    private function combinateAndOr($aLinkIds) {
        $iCountOfWheres = count($this->aWhere);

        if ($iCountOfWheres == 1) {
            return $aLinkIds[0][self::_DATA];
        }

        $aRetval = $aLinkIds[0][self::_DATA];

        for ($i = 1; $i < $iCountOfWheres; $i++) {
            $sWhereAndOr = $aLinkIds[$i][self::_AND_OR];
            switch ($sWhereAndOr) {
                case self::_AND:
                    $aRetval = array_intersect_key($aRetval, $aLinkIds[$i][self::_DATA]);
                    break;
                case self::_OR:
                    $aRetval = $aRetval + $aLinkIds[$i][self::_DATA];
                    break;
            }
        }
        return $aRetval;

    }

    /**
     * search
     *
     * @return array
     */
    private function search() {
        $aLinkIds = array();
        $iCurrent = 0;

        foreach ($this->aWhere as $sNull => $aWhereArray) {

            $sWhereType = $aWhereArray[self::_TYPE];
            $sWhereAndOr = $aWhereArray[self::_AND_OR];
            $aLinkIds[$iCurrent][self::_AND_OR] = $sWhereAndOr;

            switch ($sWhereType) {
                case self::WHERE_EQUALS:
                    $iSearchValueLength = strlen($aWhereArray[self::_VALUE]);

                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchEqualsOrLike($aWhereArray, $iSearchValueLength);
                    break;

                case self::WHERE_LIKE:
                    $iSearchValueLength = strlen($aWhereArray[self::_VALUE]);
                    $iPosOfProcentualCharacter = strpos($aWhereArray[self::_VALUE], '%');
                    if ($iPosOfProcentualCharacter === FALSE) {
                        $iPosOfProcentualCharacter = $iSearchValueLength;
                    }

                    $aWhereArray[self::_VALUE] = str_replace('%', '', $aWhereArray[self::_VALUE]);
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchEqualsOrLike($aWhereArray, $iPosOfProcentualCharacter);
                    break;

                case self::WHERE_GREATER:
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchGreaterOrGreaterEquals($aWhereArray, FALSE);
                    break;

                case self::WHERE_GREATER_EQUALS:
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchGreaterOrGreaterEquals($aWhereArray, TRUE);
                    break;

                case self::WHERE_LOWER:
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchLowerOrLowerEquals($aWhereArray, FALSE);
                    break;

                case self::WHERE_LOWER_EQUALS:
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchLowerOrLowerEquals($aWhereArray, TRUE);
                    break;

                case self::WHERE_IS_NULL:
                    $sSearchValue = 'null';
                    $aWhereArray[self::_VALUE] = $sSearchValue;
                    $iSearchValueLength = strlen($aWhereArray[self::_VALUE]);
                    $aLinkIds[$iCurrent][self::_DATA] = $this->searchEqualsOrLike($aWhereArray, $iSearchValueLength);
                    break;
            }
            $iCurrent++;
        }

        return $aLinkIds;
    }

    /**
     * create WhereArray
     *
     * @param string $sColumn
     * @param mixed $mValue
     * @param constants $cWhereType
     * @param constants $cAndOr
     * @return array
     */
    private function createWhereArray($sColumn, $mValue, $cWhereType, $cAndOr) {
        $aWhere = array();
        $aWhere[self::_COLUMN] = $sColumn;
        $aWhere[self::_VALUE] = $mValue;
        $aWhere[self::_TYPE] = $cWhereType;
        $aWhere[self::_AND_OR] = $cAndOr;
        return $aWhere;
    }

    /**
     * search Equals Or Like
     *
     * @param array $aWhereArray
     * @param integer $iSearchValueLength
     * @return array
     */
    private function searchEqualsOrLike($aWhereArray, $iSearchValueLength) {
        $aLinkIdsToFoundCount = cMemCacheIndex::getLinkIdsForSearchValue($this->oMemModel, $aWhereArray[self::_COLUMN], $aWhereArray[self::_VALUE]);
        $aLinkIdResult = array();
        foreach ($aLinkIdsToFoundCount as $sLinkId => $iFoundCount) {
            if ($iFoundCount === $iSearchValueLength) {
                $aLinkIdResult[$sLinkId] = null;
            }
        }
        return $aLinkIdResult;
    }

    /**
     * search Greater or GreaterEquals
     *
     * @param array $aWhereArray
     * @param boolean $bWithEquals
     * @return array
     */
    private function searchGreaterOrGreaterEquals($aWhereArray, $bWithEquals) {
        $aResultLinkIds = array();
        $sSearchValue = $aWhereArray[self::_VALUE];
        $iSearchValueLength = strlen($aWhereArray[self::_VALUE]);

        // start LinkIds, die mehr ziffern haben
        $aResultLinkIds = array();
        $aSearchIndex = array();
        for ($i = 0; $i < 10; $i++) {
            $aSearchIndex[$iSearchValueLength] = $i;
            $aResultLinkIds = $aResultLinkIds + cMemCacheIndex::getLinkIdsForSearchIndex($this->oMemModel, $aWhereArray[self::_COLUMN], $aSearchIndex);
        }

        // start LinkIds, die min. genauso viele ziffern haben
        $aLinkIdsWithNumberLengthOfSearchValue = array();
        $aSearchIndex = array();
        for ($i = 0; $i < 10; $i++) {
            $aSearchIndex[$iSearchValueLength-1] = $i;
            $aLinkIdsWithNumberLengthOfSearchValue = $aLinkIdsWithNumberLengthOfSearchValue + cMemCacheIndex::getLinkIdsForSearchIndex($this->oMemModel, $aWhereArray[self::_COLUMN], $aSearchIndex);
        }

        // entferne die übergroßen aus den LinkIds mit den gleicher anzahl der ziffern
        $aLinkIdsWithNumberLengthOfSearchValue = array_diff_key($aLinkIdsWithNumberLengthOfSearchValue, $aResultLinkIds);

        $aAllLinkIds = array();
        for ($i = 0; $i < $iSearchValueLength; $i++) {
            $iCurrentSearchNumeric = substr($sSearchValue, $i, 1);

            if ($bWithEquals && ($i+1) == $iSearchValueLength) {
                $iCurrentSearchNumeric--;
            }

            $aSearchIndex = array();
            $aTempLinkIds = array();
            for ($k = $iCurrentSearchNumeric; $k < 9; $k++) {
                $aSearchIndex[$i] = $k+1;
                $aTempLinkIds = $aTempLinkIds + cMemCacheIndex::getLinkIdsForSearchIndex($this->oMemModel, $aWhereArray[self::_COLUMN], $aSearchIndex);
            }

            $aHigherLinkIds = array_intersect_key($aLinkIdsWithNumberLengthOfSearchValue, $aTempLinkIds);            
            $aResultLinkIds = $aResultLinkIds + $aHigherLinkIds;
            $aLinkIdsWithNumberLengthOfSearchValue = array_diff_key($aLinkIdsWithNumberLengthOfSearchValue, $aHigherLinkIds);
            //cVardump::dump($aLinkIdsWithNumberLengthOfSearchValue, 'diese bleiben übrig');

            // gleichheitscheck auf aktuelle zifer
            $aSearchIndex[$i] = $iCurrentSearchNumeric;
            $aTempLinkIds = array();
            $aTempLinkIds = cMemCacheIndex::getLinkIdsForSearchIndex($this->oMemModel, $aWhereArray[self::_COLUMN], $aSearchIndex);
            $aLinkIdsWithNumberLengthOfSearchValue = array_intersect_key($aLinkIdsWithNumberLengthOfSearchValue, $aTempLinkIds);
            //cVardump::dump($aLinkIdsWithNumberLengthOfSearchValue, 'haben genau die gleiche ziffer');

        }

        foreach ($aResultLinkIds as $iLinkId => $sNull) {
            $aResultLinkIds[$iLinkId] = null;
        }

        return $aResultLinkIds;
    }

    /**
     * search Lower or LowerEquals
     *
     * @param array $aWhereArray
     * @param boolean $bWithEquals
     * @return array
     */
    private function searchLowerOrLowerEquals($aWhereArray, $bWithEquals) {
        $aResultLinkIds = array();
        $sSearchValue = $aWhereArray[self::_VALUE];
        $iSearchValueLength = strlen($aWhereArray[self::_VALUE]);

        // alle linkIds
        $aResultLinkIds = array();
        $aSearchIndex = array();
        for ($i = 0; $i < 10; $i++) {
            $aSearchIndex[0] = $i;
            $aResultLinkIds = $aResultLinkIds + cMemCacheIndex::getLinkIdsForSearchIndex($this->oMemModel, $aWhereArray[self::_COLUMN], $aSearchIndex);
        }


        $aGreater = $this->searchGreaterOrGreaterEquals($aWhereArray, $bWithEquals);
        $aResultLinkIds = array_diff_key($aResultLinkIds, $aGreater);

        if (!$bWithEquals) {
            $aResultLinkIds = array_diff_key($aResultLinkIds, $this->searchEqualsOrLike($aWhereArray, $sSearchValue));
        }

        foreach ($aResultLinkIds as $iLinkId => $sNull) {
            $aResultLinkIds[$iLinkId] = null;
        }

        return $aResultLinkIds;
    }
}